home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Info-Mac 3
/
Info_Mac_1994-01.iso
/
Development
/
Information
/
Mac Programming Secrets 1.0.1
/
Chapter 07
/
Neat Stuff.c
< prev
next >
Wrap
C/C++ Source or Header
|
1992-05-19
|
37KB
|
1,089 lines
#include "Standard Stuff.h"
#include "Neat Stuff.h"
#include "ZoomRect.h"
/*******************************************************************************
Global variables
*******************************************************************************/
short gUntitledWindowNumber = 0; /* Number for next new window. These
numbers start at zero and keep
going up; they are not recycled. */
WindowPtr gFirstWindow; /* We keep track of our windows in
the order in which they were
created. This variable points to
the first (oldest) window. */
/* Used when matching up windows to their menu items, and vice-versa. */
short gMenuItem;
Boolean gDoneCounting;
WindowPtr gFoundWindow;
WindowPtr gTargetWindow;
/* Variables set up by ForEachWindowDo() and ForEachWindowPerScreenDo() */
short gWindowNumber; /* Ordinal number of the current
window on the current monitor. */
short gNumberOfWindows; /* Total number of windows on the
current monitor. */
GDHandle gScreenDevice; /* Reference to the current monitor. */
Rect gScreenRect; /* Bounds of the current monitor. Takes
into account the menubar if this
is the main monitor. */
/* Variables set up and shared by tiling and stacking routines. */
short gScreenWidth; /* Width of the current monitor. */
short gScreenHeight; /* Height of the current monitor. */
short gNewWindowWidth; /* When tiling or stacking, every
window is essentially the same
size. This variable holds the
window’s new width. */
short gNewWindowHeight; /* Ditto, but for height. */
/*******************************************************************************
DoNewWindow
Create a window from a ‘WIND’ resource. If that succeeds, show the window
and add it to the Windows menu. When showing the window, using a zoom
effect that zooms the window from the Apple menu to its final position.
By passing in NIL as the window pointer in the call to GetNewWindow, we
tell the system to create a window record on the heap for us. This means
that we must close this window with a call to DisposeWindow rather than
CloseWindow.
*******************************************************************************/
void DoNewWindow(void)
{
WindowPtr newWindow;
Str255 untitledTitle;
Str255 numberAsString;
Rect rect1, rect2;
newWindow = GetNewWindow(kNewWindowID, NIL, (WindowPtr) -1);
if (newWindow != NIL) {
GetIndString(untitledTitle, rMiscStrings, sUntitledTitle);
NumToString(++gUntitledWindowNumber, numberAsString);
CatenatePStrings(untitledTitle, numberAsString);
SetWTitle(newWindow, untitledTitle);
SetRect(&rect1, 0, 0, 0, 0);
rect2 = GetWindowStructureRect(newWindow);
ZoomRect(rect1, rect2, kZoomOut);
ShowWindow(newWindow);
AddWindowToMenu(newWindow);
}
}
/*******************************************************************************
DoCloseWindow
Remove the specified window from the Windows menu, and close it by calling
DisposeWindow. When closing the window, reverse the zooming effect we did
when opening the window.
Note that we call DisposeWindow rather than CloseWindow. Because we passed
in NIL as the window record storage when we create the window, we
dynamically allocated the record in the application’s heap. To free up
that record, we must call DisposeWindow.
*******************************************************************************/
void DoCloseWindow(WindowPtr theWindow)
{
Rect rect1, rect2;
SetRect(&rect1, 0, 0, 0, 0);
rect2 = GetWindowStructureRect(theWindow);
RemoveWindowFromMenu(theWindow);
DisposeWindow(theWindow);
ZoomRect(rect2, rect1, kZoomIn);
}
/*******************************************************************************
DoActivateWindow
When a window becomes active, it’s almost the same as saying that it is
becoming the frontmost window. Similarly, when a window is being
deactivated, it’s almost the same as saying that another window is coming
to the front. Therefore, when a window is activated or deactivated, we
adjust where the checkmark is placed in the Windows menu.
*******************************************************************************/
void DoActivateWindow(WindowPtr theWindow, Boolean becomingActive)
{
short menuItem;
MenuHandle windowsMenu;
windowsMenu = GetMHandle(mWindows);
menuItem = GetMenuItemForWindow(theWindow);
if (becomingActive)
SetItemMark(windowsMenu, menuItem, checkMark);
else
SetItemMark(windowsMenu, menuItem, noMark);
}
/*******************************************************************************
DoZoomWindow
This routine gets called when the user clicks in the zoom box of a window.
It’s our own special ZoomWindow procedure that zooms the window to the
screen that contains the majority of the window.
DoZoomWindow works by getting three pertinent rectangles: the bounds of
the monitor the window is on, the bounds of the window’s frame (its
structure), and the bounds of the window’s content area. Using these
rectangles, we calculate the where we want the content rectangle of the
zoomed window to be. We take that resulting rectangle and jam it into a
structure that the default WDEF maintains that helps it zoom windows in
and out. Once all that is done, we call ZoomWindow. ZoomWindow will zoom
the window to the size and location we just calculated.
Note that in these days of the ‘90’s, there is another step that you might
want to take if you want your application to get all those Ooh’s and Aah’s
from the customers. With monitors getting larger and larger, it’s a good
idea to zoom your window to be only as large as it needs to be. For
instance, if the user zooms a word processing document on a 19 inch
monitor, it would be nice to zoom the window’s width out to 8.5 inches
rather than the full 13 inches. We don’t do this here, but it’s something
for you to think about.
*******************************************************************************/
void DoZoomWindow(WindowPtr window, short zoomDir, Boolean front)
{
Rect contentRect, structureRect, deviceRect;
/* If there is the possibility of multiple gDevices, we must check
them to make sure we are zooming to the right display device. */
if ((zoomDir == inZoomOut) && (gMac.hasColorQD)) {
contentRect = GetWindowContentRect(window);
structureRect = GetWindowStructureRect(window);
deviceRect = GetWindowDeviceRectNMB(window);
deviceRect.left += (contentRect.left - structureRect.left + 2);
deviceRect.top += (contentRect.top - structureRect.top + 2);
deviceRect.right -= (structureRect.right - contentRect.right + 2);
deviceRect.bottom -= (structureRect.bottom - contentRect.bottom + 2);
(*(WStateDataHandle)(((WindowPeek)window)->dataHandle))->stdState = deviceRect;
}
ZoomWindow(window, zoomDir, front);
}
/*******************************************************************************
DoTileWindows
Call ForEachWindowPerScreenDo, which is a routine that allows us to
perform a specified operation on every window. Our task here is to tile
all the windows, making sure that each is the same size, but in
non-overlapping locations. In our TileSetup routine, we calculate the
windows’ size and save it off. In our TileTheWindow routine, we set each
window to that size, and position it in the right place.
*******************************************************************************/
void DoTileWindows(void)
{
ForEachWindowPerScreenDo(TileSetup, TileTheWindow, NIL);
}
const short kMinHeight = 100;
const short kMinWidth = 100;
const short kGapBetweenWindows = 2;
short pMaxWindowsPerRow;
short pMaxWindowsPerColumn;
short pNumberOfColumns;
short pNumberOfRows;
//
// This function is called once per monitor by ForEachWindowPerScreenDo.
// Based on the size of the screen and the number of windows that are to
// be tiled on the screen, we determine the number of rows and columns
// that we want to tile the windows into. We also determine the size
// each window on this screen should be.
//
void TileSetup(void)
{
if (gNumberOfWindows > 0) {
gScreenRect.top += kGapBetweenWindows;
gScreenRect.left += kGapBetweenWindows;
gScreenWidth = gScreenRect.right - gScreenRect.left;
gScreenHeight = gScreenRect.bottom - gScreenRect.top;
pMaxWindowsPerRow = gScreenWidth / kMinWidth;
pMaxWindowsPerColumn = gScreenHeight / kMinHeight;
pNumberOfColumns = (gNumberOfWindows-1) / pMaxWindowsPerColumn + 1;
pNumberOfRows = (gNumberOfWindows-1) / pNumberOfColumns + 1;
gNewWindowWidth = gScreenWidth / pNumberOfColumns;
gNewWindowHeight = gScreenHeight / pNumberOfRows;
}
}
//
// Main window cruncher for tiling. This routine is called once for every
// window. Makes the given window the size we determined in TileSetUp, and
// place it in the appropriate row and column based on its ordering in the
// window list.
//
// Note that our calculations are set up to determine the size and location
// of the windows _frame_, not its content rectangle (i.e., its portRect).
// However, MoveWindow and SizeWindow like to work with values that _do_
// affect the portRect. To solve this problem, we do the actual moving and
// resizing by creating a routine called SetWindowBounds. This utility
// routine translates the frame rectangle into the content rectangle for us
// before calling MoveWindow and SizeWindow.
//
void TileTheWindow(WindowPtr theWindow)
{
Rect newBounds;
short row, column;
row = (gWindowNumber-1) / pNumberOfColumns;
column = (gWindowNumber-1) % pNumberOfColumns;
newBounds.left = gScreenRect.left + column * gNewWindowWidth;
newBounds.top = gScreenRect.top + row * gNewWindowHeight;
newBounds.right = newBounds.left + gNewWindowWidth - kGapBetweenWindows;
newBounds.bottom = newBounds.top + gNewWindowHeight - kGapBetweenWindows;
SetWindowBounds(theWindow, newBounds);
}
/*******************************************************************************
DoStackWindows
Call ForEachWindowPerScreenDo, which is a routine that allows us to
perform a specified operation on every window. Our task here is to stack
all the windows. In our StackSetup routine, we calculate the number of
diagonals we’ll have on the monitor we are tiling on. We also calculate an
initial window size, a size which gets smaller as subsequent windows get
stacked further down and to the right. In our TileTheWindow routine, we
set each window to the appropriate size and position.
*******************************************************************************/
void DoStackWindows(void)
{
ForEachWindowPerScreenDo(StackSetup, StackTheWindow, NIL);
}
const short kVerticalStagger = 20;
const short kHorizontalStagger = 10;
const short kWindowsPerDiagonal = 8;
//
// This function is called once per monitor by ForEachWindowPerScreenDo.
// Its purpose is two-fold. First, it figures out how many diagonals of
// windows will be needed to stack all of the windows on the current screen.
// Right now, we permit only 8 windows to be stacked in a diagonal before we
// need to create a new diagonal of windows that’s move over to the right a
// little bit.
//
// The second thing this function does is figure out a good initial size for
// the stacked windows. This size shrinks for windows that are stacked
// further down and to the right on the screen. The calculations for this
// shrinking are in the StackTheWindow function.
//
void StackSetup(void)
{
short maxWindowsInDiagonal;
short numberOfBottomWindows;
InsetRect(&gScreenRect, kGapBetweenWindows, kGapBetweenWindows);
// Find the height and width of this window
gScreenWidth = gScreenRect.right - gScreenRect.left;
gScreenHeight = gScreenRect.bottom - gScreenRect.top;
// Find the longest diagonal of windows we will be dealing
// with. This will either be kWindowsPerDiagonal or
// gNumberOfWindows, whichever is smaller.
maxWindowsInDiagonal = gNumberOfWindows;
if (maxWindowsInDiagonal > kWindowsPerDiagonal)
maxWindowsInDiagonal = kWindowsPerDiagonal;
// Find out how many windows will end up in the last
// position of a diagonal. This number is crucial in
// determining the horizontal span of all the windows,
// which is used in calculating the windows’ width.
numberOfBottomWindows = (gNumberOfWindows / kWindowsPerDiagonal);
if (numberOfBottomWindows > 0)
--numberOfBottomWindows;
// Figure out the size of the first window to be stacked.
// We start off with the the size of the entire screen and
// start trimming it back to allow for other windows. First,
// we trim back the width by an amount equal to the number
// of pixels that the rightmost window will be from the
// left edge of the screen. Next, we trim back the height so
// that we can accommodate the tallest diagonal we’ll be dealing
// with.
gNewWindowWidth = gScreenWidth
- (((maxWindowsInDiagonal - 1) + numberOfBottomWindows)
* kHorizontalStagger);
gNewWindowHeight = gScreenHeight
- (maxWindowsInDiagonal - 1) * kHorizontalStagger;
}
//
// This routine is called once for each window. Its function is to calculate
// the size and location for each window. First, it determines which diagonal
// the specified window should be in and its position within that diagonal.
// Then it determines the horizontal position for the window; as each window
// is always moved to the right from the previous window by a constant
// amount, the horizontal position is a simple function of the window’s
// creation rank. Next, we determine the vertical position of the window.
// This, too, is a simple function, this time of the window’s position in the
// diagonal it will be placed in. What this means is that the 1st, 9th, 17th,
// etc., windows will all have the same vertical position. Similarly for the
// 2nd, 10th, 18th, etc., windows, and so on.
//
// Next, we have to determine the size of the window. First, we consider the
// width. Each window in a diagonal is the same size. This size is determined
// by taking the initial window size calculated in StackSetup, and
// subtracting an amount that is a function of the diagonal number we are
// working on. First, we subtract an amount such that the right edge of each
// window in this diagonal will line up with the corresponding window in the
// previous diagonal. Next, we add back 10 pixels just to give a nice
// staggering effect on the right hand side as well as the left. After doing
// that, we work on figuring the height of the window. We start off with the
// standard height calculated in StackSetup. Then we subtract a multiple of
// 10 pixels that is based on how far down in the diagonal the window is.
// This means that the height of the window is a function of the window’s
// position in the diagonal, while the width of the window is a function of
// the position of the diagonal itself.
//
void StackTheWindow(WindowPtr theWindow)
{
Rect newBounds;
short placeInDiagonal, diagonal;
placeInDiagonal = (gWindowNumber - 1) % kWindowsPerDiagonal;
diagonal = (gWindowNumber - 1) / kWindowsPerDiagonal;
newBounds.left = gScreenRect.left + (gWindowNumber - 1) * kHorizontalStagger;
newBounds.top = gScreenRect.top + placeInDiagonal * kVerticalStagger;
newBounds.right = newBounds.left + gNewWindowWidth - diagonal * (kWindowsPerDiagonal * kHorizontalStagger - 10);
newBounds.bottom = newBounds.top + gNewWindowHeight - placeInDiagonal * 10;
SetWindowBounds(theWindow, newBounds);
}
/*******************************************************************************
DoSelectFromWindowsMenu
Called when the user selects one of the menu items containing a window’s
name (in the Windows menu). We stash off the menu item that was selected,
and then start iterating over all the windows in chronological order.
Since the list of window names in the Windows menu is also maintained in
chronological order, we know that there is a direct relationship between
the menu item number and the window’s chronological rank. In other words,
if we selected the Xth item in the Windows menu (not including the Tile or
Stack menu items), all we have to do is find the Xth oldest window and
select it.
*******************************************************************************/
void DoSelectFromWindowsMenu(short menuItem)
{
gMenuItem = menuItem - iFirstWindow + 1;
ForEachWindowDo(NIL, LookForSelectedWindow, NIL);
}
void LookForSelectedWindow(WindowPtr theWindow)
{
if (--gMenuItem == 0)
SelectWindow(theWindow);
}
/*******************************************************************************
AddWindowToMenu
Called to add a newly created window to the Windows menu. We make that
addition by making a call to AppendMenu to create the new menu item, and
then SetItem to set that item’s name. The reason why we don’t let
AppendMenu set the name is because of the meta-character processing that
AppendMenu performs. If our window’s name happened to have something like
a “!” or “/” it it, what appeared in the menu wouldn’t be what we
expected. SetItem doesn’t do recognize the meta-characters, so we avoid
that problem when calling it.
Next, we insert our window into our own private window list. Normally, you
don’t need to do this. The Window Manager keeps track of all the windows
on the screen so that you don’t have to. However, the list it manages
links the windows in front to back order. In other words, the frontmost
window is at the front of the list, the record for the window behind it is
second in the list, and so on.
In order to manage our Windows menu, we’d really like to have a list of
windows in chronological order. We do this by using the refCon field of
the window record. Each window’s refCon field will point to the next
youngest window. The oldest window (the head of the chain) will be kept in
the global variable gFirstWindow. The refCon field of the last window is
NIL. If there are no windows, gFirstWindow is NIL.
*******************************************************************************/
void AddWindowToMenu(WindowPtr theWindow)
{
Str255 title;
MenuHandle windowsMenu;
WindowPtr lastWindow;
GetWTitle(theWindow, title);
windowsMenu = GetMenu(mWindows);
AppendMenu(windowsMenu, "\pNeed something here or call doesn’t work.");
SetItem(windowsMenu, CountMItems(windowsMenu), title);
lastWindow = GetPreviouslyCreatedWindow(NIL); /* NIL means “get last window” */
if (lastWindow != NIL)
SetWRefCon(lastWindow, (long) theWindow);
else
gFirstWindow = theWindow;
}
/*******************************************************************************
RemoveWindowFromMenu
Get the menu item that corresponds to this window, and remove that menu
item from the Windows menu. Remove the window from our chronological list
of windows.
*******************************************************************************/
void RemoveWindowFromMenu(WindowPtr theWindow)
{
MenuHandle windowsMenu;
WindowPtr previousWindow;
windowsMenu = GetMenu(mWindows);
DelMenuItem(windowsMenu, GetMenuItemForWindow(theWindow));
if (theWindow == gFirstWindow) {
gFirstWindow = (WindowPtr) GetWRefCon(theWindow);
} else {
previousWindow = GetPreviouslyCreatedWindow(theWindow);
SetWRefCon(previousWindow, GetWRefCon(theWindow));
}
SetWRefCon(theWindow, 0);
}
/*******************************************************************************
ForEachWindowDo
Routine that iterates over all of the windows. It first counts up the
number of windows on the monitor we are currently examining. Next, it
calls a setup routine provided by the caller. After that, it calls an
action procedure for each window. This action procedure is passed a
pointer to the current window we are iterating over. It is also able to
access the total number of windows and the window’s rank through the
global variables gNumberOfWindows and gWindowNumber. After all windows
have been acted upon, a clean up routine provided by the caller is called.
Note that windows are iterated in chronological order, as maintained by
the links stored in the their refCon fields.
*******************************************************************************/
void ForEachWindowDo(SetUpProc theStarter,
WindowActionProc theDoer,
FinishUpProc enderWiggin)
{
WindowPtr currentWindow;
Boolean haveAWindow;
Boolean windowIsOurConcern;
// To get the number of windows, we recursively call ourself
// with an action procedure that simply increments a counter.
// So that we don’t infinitely recurse, we check the action
// procedure to see if it’s our counting routine. If not,
// we don’t recurse.
if (theDoer != CountWindows) {
gNumberOfWindows = 0;
ForEachWindowDo(NIL, CountWindows, NIL);
}
if ((theDoer == CountWindows) || (gNumberOfWindows > 0)) {
// Start keeping track of window rank
gWindowNumber = 0;
// If the caller provided a setup routine, call it.
if (theStarter != NIL)
(*theStarter)();
// Start iterating over all the windows. Skip over any DA’s or
// dialog windows. The expression that sets “windowIsOurConcern”
// could be modified to also skip over invisible windows.
if (theDoer != NIL) {
currentWindow = gFirstWindow;
while (currentWindow != NIL) {
windowIsOurConcern = IsAppWindow(currentWindow);
if (windowIsOurConcern) {
++gWindowNumber;
(*theDoer)(currentWindow);
}
currentWindow = (WindowPtr) GetWRefCon(currentWindow);
}
}
// Done with all the windows. If the caller
// provided a cleanup routine, call it.
if (enderWiggin != NIL)
(*enderWiggin)();
}
}
/*******************************************************************************
ForEachWindowPerScreenDo
Ugly-ass routine that iterates over all of the windows, but in a peculiar
fashion. What it does is first iterate over all the monitors hooked up to
the machine. For each monitor, it first counts up the number of windows on
the monitor we are currently examining. Next, it calls a setup routine
provided by the caller. After that, it calls an action procedure for each
window on the monitor. This action procedure is passed a pointer to the
current window we are iterating over. It is also able to access the handle
to the current monitor, the size of the current monitor, the number of
windows on the current monitor, and the window’s rank on the current
monitor through the global variables gScreenDevice, gScreenRect,
gNumberOfWindows, and gWindowNumber. After all windows on the monitor have
been acted upon, a clean up routine provided by the caller is called.
Finally, we move on to the next monitor.
Note that windows are iterated in chronological order, as maintained by
the links stored in the their refCon fields.
*******************************************************************************/
void ForEachWindowPerScreenDo(SetUpProc theStarter,
WindowActionProc theDoer,
FinishUpProc enderWiggin)
{
WindowPtr currentWindow;
Boolean haveAWindow;
Boolean windowIsOurConcern;
GDHandle currentScreenDevice;
if (gMac.hasColorQD)
currentScreenDevice = GetDeviceList();
else
currentScreenDevice = NIL;
do {
// To get the number of windows on the current monitor of
// interest, we recursively call ourself with an action
// procedure that simply increments a counter. So that we
// don’t infinitely recurse, we check the action procedure
// to see if it’s our counting routine. If not, we don’t recurse.
if (theDoer != CountWindows) {
gNumberOfWindows = 0;
ForEachWindowPerScreenDo(NIL, CountWindows, NIL);
}
if ((theDoer == CountWindows) || (gNumberOfWindows > 0)) {
// Start keeping track of window rank on this monitor
gWindowNumber = 0;
// Export the handle to the current device
gScreenDevice = currentScreenDevice;
// Export the size of the monitor. If we have a valid device
// handle, use it to get the device’s size. If we don’t have
// a valid handle (it is NIL -- indicating that we are running
// on a machine without Color QuickDraw), get the size of the
// screen from GetMainScreenRect() (which will effectively
// return qd.screenBits.bounds in this case).
if (currentScreenDevice != NIL) {
gScreenRect = (**currentScreenDevice).gdRect;
if (currentScreenDevice = GetMainDevice())
gScreenRect.top += GetMBarHeight();
} else {
gScreenRect = GetMainScreenRect();
gScreenRect.top += GetMBarHeight();
}
// If the caller provided a setup routine, call it.
if (theStarter != NIL)
(*theStarter)();
// Start iterating over all the windows. If the majority of the
// windows is on the monitor we are currently looking at from
// our outer loop, pass that window to the caller’s action
// procedure. Skip over any DA’s or dialog windows. The expression
// that sets “windowIsOurConcern” could be modified to also skip
// over invisible windows.
if (theDoer != NIL) {
currentWindow = gFirstWindow;
while (currentWindow != NIL) {
windowIsOurConcern = IsAppWindow(currentWindow) &&
(currentScreenDevice == GetWindowDevice(currentWindow));
if (windowIsOurConcern) {
++gWindowNumber;
(*theDoer)(currentWindow);
}
currentWindow = (WindowPtr) GetWRefCon(currentWindow);
}
}
// Done with all the windows on this monitor. If the caller
// provided a cleanup routine, call it.
if (enderWiggin != NIL)
(*enderWiggin)();
}
// Do the next monitor
if (gMac.hasColorQD)
currentScreenDevice = GetNextDevice(currentScreenDevice);
} while (currentScreenDevice != NIL);
}
/*******************************************************************************
CountWindows
This is a WindowActionProc called by ForEachWindowPerScreenDo and
ForEachWindowDo to count windows.
*******************************************************************************/
void CountWindows(WindowPtr theWindow)
{
++gNumberOfWindows;
}
/*******************************************************************************
GetPreviouslyCreatedWindow
Called to return the window immediately before the target window in our
chronological list of windows. This is handy for searching backwards when
working with a singly linked list of records.
*******************************************************************************/
WindowPtr GetPreviouslyCreatedWindow(WindowPtr theWindow)
{
gFoundWindow = NIL;
gTargetWindow = theWindow;
ForEachWindowDo(NIL, LookForPreviousWindow, NIL);
return gFoundWindow;
}
void LookForPreviousWindow(WindowPtr theWindow)
{
if (gTargetWindow == (WindowPtr) GetWRefCon(theWindow))
gFoundWindow = theWindow;
}
/*******************************************************************************
GetMenuItemForWindow
Given a window pointer, return the number for the Windows menu item that
corresponds to it. This is done by iterating over all our windows in
chronological order, incrementing a counter as we go. When we get to the
window we are interested in, we stop counting. This means that our counter
ends up hold the chronological rank of our window. This rank is then added
to iFirstWindow to give us the appropriate menu item number.
*******************************************************************************/
short GetMenuItemForWindow(WindowPtr theWindow)
{
gDoneCounting = FALSE;
gTargetWindow = theWindow;
gMenuItem = iFirstWindow - 1;
ForEachWindowDo(NIL, CountSomeWindow, NIL);
return gMenuItem;
}
void CountSomeWindow(WindowPtr theWindow)
{
if (!gDoneCounting) {
++gMenuItem;
if (theWindow == gTargetWindow)
gDoneCounting = TRUE;
}
}
/*******************************************************************************
SetWindowBounds
Set the size and location of the given window’s FRAME. Note that this
differs from a simple MoveWindow/SizeWindow combination in that the
parameters passed to those routines work on the window’s content
rectangle, not the outer frame rectangle.
What we do to accomplish this is to take the frame rectangle passed into
this routine and calculate what the content rectangle would be for a
window that had that frame’s size. This is done by looking at the
window’s current strucRgn (the region that describes the window’s frame)
and contRgn (the region that describes the inside content area of the
window). The difference in size between these two regions is determined,
and is then applied to “newBounds”. This gives us a rectangle that can be
passed to MoveWindow and SizeWindow.
Note that we first hide the window before changing its bounds. This is so
that we don’t first see the effect of MoveWindow, and then the effect of
SizeWindow.
*******************************************************************************/
void SetWindowBounds(WindowPtr theWindow, Rect newBounds)
{
short top;
short left;
short height;
short width;
short topInset;
short leftInset;
short bottomInset;
short rightInset;
Rect oldBounds;
RgnHandle contRgn;
RgnHandle structRgn;
contRgn = ((WindowPeek) theWindow)->contRgn;
structRgn = ((WindowPeek) theWindow)->strucRgn;
oldBounds = (**structRgn).rgnBBox;
if (!EqualRect(&oldBounds, &newBounds)) {
topInset = (**contRgn).rgnBBox.top - (**structRgn).rgnBBox.top;
leftInset = (**contRgn).rgnBBox.left - (**structRgn).rgnBBox.left;
bottomInset = (**structRgn).rgnBBox.bottom - (**contRgn).rgnBBox.bottom;
rightInset = (**structRgn).rgnBBox.right - (**contRgn).rgnBBox.right;
top = newBounds.top + topInset;
left = newBounds.left + leftInset;
height = newBounds.bottom - top - bottomInset;
width = newBounds.right - left - rightInset;
HideWindow(theWindow);
MoveWindow(theWindow, left, top, FALSE);
SizeWindow(theWindow, width, height, TRUE);
ZoomRect(oldBounds, newBounds, kLinear);
SelectWindow(theWindow);
ShowWindow(theWindow);
}
}
/*******************************************************************************
GetWindowContentRect
Given a window pointer, return the global rectangle that encloses the
content area of the window.
*******************************************************************************/
Rect GetWindowContentRect(WindowPtr window)
{
WindowPtr oldPort;
Rect contentRect;
GetPort(&oldPort);
SetPort(window);
contentRect = window->portRect;
LocalToGlobalRect(&contentRect);
SetPort(oldPort);
return contentRect;
}
/*******************************************************************************
GetWindowStructureRect
This procedure is used to get the rectangle that surrounds the entire
structure of a window. This works whether or not the window is visible. If
the window is visible, it is a simple matter of using the bounding
rectangle of the structure region. If the window is invisible, the
strucRgn is not valid. To make it valid, the window has to be moved way
off the screen and then made visible. This generates a valid strucRgn,
although it is valid for the position that is way off the screen. It still
needs to be offset back into the original position. Once the bounding
rectangle for the strucRgn is obtained, the window can then be hidden
again and moved back to its correct location. Note that ShowHide is used,
instead of ShowWindow and HideWindow. HideWindow can change the plane of
the window. Also, ShowHide does not affect the hiliting of windows.
*******************************************************************************/
Rect GetWindowStructureRect(WindowPtr window)
{
const short kOffscreenLocation = 0x4000; /* Halfway to infinity */
GrafPtr oldPort;
Rect structureRect;
Point windowLoc;
if (((WindowPeek)window)->visible)
structureRect = (*(((WindowPeek)window)->strucRgn))->rgnBBox;
else {
GetPort(&oldPort);
SetPort(window);
windowLoc = GetGlobalTopLeft(window);
MoveWindow(window, windowLoc.h, kOffscreenLocation, FALSE);
ShowHide(window, TRUE);
structureRect = (*(((WindowPeek) window)->strucRgn))->rgnBBox;
ShowHide(window, FALSE);
MoveWindow(window, windowLoc.h, windowLoc.v, FALSE);
OffsetRect(&structureRect, 0, windowLoc.v - kOffscreenLocation);
SetPort(oldPort);
}
return structureRect;
}
/*******************************************************************************
GetWindowDeviceRectNMB (No Menu Bar)
Given a window pointer, find the device that contains most of the window
and return that device’s bounding rectangle. If this device is the main
device, remove the menubar area from the rectangle.
*******************************************************************************/
Rect GetWindowDeviceRectNMB(WindowPtr window)
{
Rect deviceRect, tempRect;
deviceRect = GetWindowDeviceRect(window);
tempRect = GetMainScreenRect();
if (EqualRect(&deviceRect, &tempRect))
deviceRect.top += GetMBarHeight();
return deviceRect;
}
/*******************************************************************************
GetWindowDeviceRect
Given a window pointer, find the device that contains most of the window
and return that device’s bounding rectangle.
*******************************************************************************/
Rect GetWindowDeviceRect(WindowPtr window)
{
if (gMac.hasColorQD)
return (*GetWindowDevice(window))->gdRect;
else
return GetMainScreenRect();
}
/*******************************************************************************
GetWindowDevice
Given a window pointer, find the device that contains most of the window
and return its handle.
*******************************************************************************/
GDHandle GetWindowDevice(WindowPtr window)
{
return GetRectDevice(GetWindowStructureRect(window));
}
/*******************************************************************************
GetRectDevice
Given a rectangle in global coordinates, find the monitor it overlaps the
most. This is done by iterating over all of the monitors with the
GetDeviceList and GetNextDevice calls. For each device, we find the area
of overlap with the given rectangle. The device that results in the
greatest overlap is returned to the caller.
This routine assumes the existence of the Graphics Device Manager.
*******************************************************************************/
GDHandle GetRectDevice(Rect globalRect)
{
long area;
long maxArea;
GDHandle device;
GDHandle deviceToReturn;
Rect intersection;
deviceToReturn = GetMainDevice(); /* Use as default choice. */
maxArea = 0;
for (device = GetDeviceList(); device != NIL; device = GetNextDevice(device)) {
if (TestDeviceAttribute(device, screenDevice)
&& TestDeviceAttribute(device, screenActive)
&& SectRect(&globalRect, &((*device)->gdRect), &intersection)) {
area = (intersection.right - intersection.left) *
(intersection.bottom - intersection.top);
if (area > maxArea) {
deviceToReturn = device;
maxArea = area;
}
}
}
return deviceToReturn;
}
/*******************************************************************************
LocalToGlobalRect
Convert a rectangle from local coordinates to global coordinates. Like
QuickDraw’s LocalToGlobal, it assumes that the current port is set
correctly.
*******************************************************************************/
void LocalToGlobalRect(Rect *aRect)
{
LocalToGlobal(&topLeft(*aRect));
LocalToGlobal(&botRight(*aRect));
}
/*******************************************************************************
GetGlobalTopLeft
Return the top left point of the given window’s port in global
coordinates. This returns the top left point of the window’s content area
only; it doesn’t include the window’s drag region (or title bar).
*******************************************************************************/
Point GetGlobalTopLeft(WindowPtr window)
{
GrafPtr oldPort;
Point globalPt;
GetPort(&oldPort);
SetPort(window);
globalPt = topLeft(window->portRect);
LocalToGlobal(&globalPt);
SetPort(oldPort);
return globalPt;
}
/*******************************************************************************
GetMainScreenRect
Gets the bounding rectangle of the main screen (the one with the menu bar
on it). This rectangle includes the area that contains the menubar. For
example, on a Mac Classic, this routine should return (0, 0, 512, 342).
*******************************************************************************/
Rect GetMainScreenRect(void)
{
GDHandle mainDevice;
GrafPtr mainPort;
if (gMac.hasColorQD) {
mainDevice = GetMainDevice();
return (*mainDevice)->gdRect;
} else {
GetWMgrPort(&mainPort);
return mainPort->portRect;
}
}
/*******************************************************************************
CatenatePStrings
Catenate two Pascal strings by attaching the second string on the end of
the first string. If you are running under MPW 3.2 or later, you can
simply use the PLCatenate library routine instead.
*******************************************************************************/
void CatenatePStrings(Str255 targetStr, Str255 appendStr)
{
short appendLen;
/* Figure out number of bytes to copy, up to 255 */
appendLen = MIN(appendStr[0], 255 - targetStr[0]);
if (appendLen > 0) {
BlockMove(appendStr+1, targetStr+targetStr[0]+1, (long) appendLen);
targetStr[0] += appendLen;
}
}